/*
Copyright (C) 2015 Marien Raat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <landMap.h>
#include <iostream>

LandMapGenerator::LandMapGenerator()
{

}

void LandMapGenerator::setWaterLevel(double waterLevel)
{
    this->waterLevel = waterLevel;
    firstLandMap = true;
    firstSurfaceHeightmap = true;
}

bool *LandMapGenerator::generateLandMap(double *heightmap,
					unsigned width,
					unsigned height,
                                        bool overwriteOldMap)
{
    // To save on re-computing time. True if below or equal to
    // sea level
    bool *belowSeaLevelMap = new bool[width * height];
    for (unsigned i = 0; i < width * height; i++)
	belowSeaLevelMap[i] = heightmap[i] <= waterLevel;

    if (firstLandMap || overwriteOldMap)
        landMap = new bool[width * height];
    else if (previousWidthLandMap != width ||
             previousHeightLandMap != height) {
        delete landMap;
        landMap = new bool[width * height];
    }
    firstLandMap = false;
    previousWidthLandMap = width;
    previousHeightLandMap = height;

    for (unsigned i = 0; i < width * height; i++) {
	unsigned x = i % width, y = i / width;
	// landMap is initialized to false if the point is below
	// sea level and it is on one of the sides of the map.
	landMap[i] = !((x == 0 || x == (width - 1) ||
                        y == 0 || y == (height - 1)) &&
                       belowSeaLevelMap[i]);
    }

    // Loop till no more new sea is found
    while (true) {
	bool foundSea = false;
	for (unsigned i = 0; i < width * height; i++) {
	    unsigned x = i % width,
		y = i / height;
	    if (!landMap[i]) {
		foundSea = spreadFrom(x, y, width, height,
                                      belowSeaLevelMap, landMap) || foundSea;
            }
	}

	if (!foundSea)
	    break;
    }

    // Free the belowSeaLevelMap
    delete belowSeaLevelMap;

    return landMap;
}

double *LandMapGenerator::generateSurfaceHeightmap(double *heightmap,
                                                   unsigned width,
                                                   unsigned height,
                                                   bool overwriteOldMap)
{
    generateLandMap(heightmap, width, height, overwriteOldMap);

    if (firstSurfaceHeightmap || overwriteOldMap)
        surfaceHeightmap = new double[width * height];
    else if (previousWidthHeightmap != width ||
             previousHeightHeightmap != height) {
        delete surfaceHeightmap;
        surfaceHeightmap = new double[width * height];
    }
    firstSurfaceHeightmap = false;
    previousWidthHeightmap = width;
    previousHeightHeightmap = height;

    for (int i = 0; i < width * height; i++)
        surfaceHeightmap[i] = landMap[i] ? heightmap[i] : waterLevel;

    return surfaceHeightmap;
}

bool LandMapGenerator::spreadFrom(unsigned x,
				  unsigned y,
				  unsigned width,
				  unsigned height,
				  bool *belowSeaLevelMap,
				  bool *landMap)
{
    bool foundSea = false;
    for (unsigned i = 0; i < 4; i++) {
	int changeX = i % 2 == 0 ? (i / 2 < 1 ? - 1 : 1) : 0,
	    changeY = i % 2 == 1 ? (i / 2 < 1 ? - 1 : 1) : 0;

        int spreadX = x + changeX, spreadY = y + changeY;
        while (true) {
            bool validLocation = spreadX >= 0 && spreadX < width && spreadY >= 0
                && spreadY < height;
            int index = spreadX + spreadY * width;
            bool spreadFromHere = validLocation && belowSeaLevelMap[index]
                             && landMap[index];
            foundSea = foundSea || spreadFromHere;
            if (spreadFromHere) landMap[index] = false;
            else break;

            spreadX += changeX;
            spreadY += changeY;
        }
    }
    return foundSea;
}
